package droidhouseclone;

import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.Observable;
import java.util.Observer;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.sound.sampled.*;

/**
 *
 * @author Lorenzo Ferri
 */
public class Playerthread implements Observer, Runnable {

    //Attributi interni alla classe
    private int volume;
    private boolean Running = true;
    private ServerSong producer;
    private int port;
    private Thread thread;
    private final int MAX_PACKET_SIZE = 24 * 1024;
    private PipedOutputStream pOut;
    private PipedInputStream pIn;
    private StateObservable models;

    public Playerthread(StateObservable model) {

        //Inizializzazione degli attributi
        port = 3000;
        this.thread = new Thread(this);
        models=model;
        model.addObserver(this);
    }

     public void run() {

        try {
                /*Vengono inizializzati i piped stream che serviranno per mantenere
                un collegamento diretto tra riproduzione e ricezione pacchetti*/
                pOut = new PipedOutputStream();
                pIn = new PipedInputStream(pOut,8*1024);

                //Inizializzo il server che dovrà che ricevarà i pacchetti del file
                producer = new ServerSong(port, MAX_PACKET_SIZE, pOut, pIn);
                //Avvio il server
                producer.startServer();

                //avvio la riproduzione che rimarra in attesa fino al arrivo dei pacchetti
                play();

            } catch (Exception e) {
                //Il player è stato interroto
                System.out.println("Riproduzione Terminata");
                
            }


    }

    public void setPort(int porta) {

        //Aggiorno il valore della porta di ascolto.
        port = porta;
        if(producer!=null)
        producer.stopServer();
        producer = new ServerSong(port, MAX_PACKET_SIZE, pOut, pIn);
        producer.startServer();

    }

    public void startPlayer() {
        //Se il thread non è attivo viene avviato
        if (!thread.isAlive()) {
            thread = new Thread(this);
            thread.start();
        }
    }
    public void stoplay() {
        //metodo che arresta il serverSong e chiude le pipe condivise
        try {
            if(producer!=null)
            {
                producer.stopServer();
                producer.closepipe();
            }
        } catch (IOException ex) {
           System.out.println("Pipe Closed");
        }
    }

    public boolean setGain(float newGain, SourceDataLine line) {
        //Valore volume
        double value = (volume) / 100.0;
        try {
            //Inizializzazione interfaccia per il settaggio del volume
            FloatControl gainControl = (FloatControl) line.getControl(FloatControl.Type.MASTER_GAIN);
            //Valore del volume in decibel
            float dB = (float) (Math.log(value == 0.0 ? 0.0001 : value) / Math.log(10.0) * 20.0);
            //Setto il volume
            gainControl.setValue(dB);
            return true;
        } catch (Exception ex) {
            System.out.println("Impossibile modificare il volume");
            return false;
        }
    }

    //Aggiorna il valore del volume
    public void setVolume(int v) {
        this.volume = v;
    }

    //Ritorna il valore del volume attuale
    public int getVolume() {
        return volume;
    }

    public void play() throws UnsupportedAudioFileException, LineUnavailableException, IOException {
       
            //Creazione flusso audio che punta alla PipedInputStream
            AudioInputStream in = AudioSystem.getAudioInputStream(pIn);
            AudioInputStream din = null;
            //Inizializzo il Formato del flusso
            AudioFormat baseFormat = in.getFormat();
            AudioFormat decodedFormat =
                    new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, //Tecnica codifica campioni
                    baseFormat.getSampleRate(),                      //numero di campioni al secondo
                    16,                                              //bit per campione
                    baseFormat.getChannels(),                        //Numero di canali stereo o mono
                    baseFormat.getChannels() * 2,                    //byte per frame
                    baseFormat.getSampleRate(),                      //frame al secondo
                    false);

            din = AudioSystem.getAudioInputStream(decodedFormat, in);
            //Inizio la riproduzione
            rawplay(decodedFormat, din);
            //Finita la riproduzione chiudo l'inputStream.
            in.close();
        
    }

    private void rawplay(AudioFormat targetFormat, AudioInputStream din) throws IOException, LineUnavailableException {

        //Attributi del metodo
        byte[] data = new byte[4096];
        //Informazioni riguardanti il SorceDateLine da inizializzare
        DataLine.Info info = new DataLine.Info(SourceDataLine.class,targetFormat );

        //Interfaccio di riproduzione
        SourceDataLine line = (SourceDataLine) AudioSystem.getLine(info);

        int nBytesRead = 0;
        
        //Apro l'interfaccia di riproduzione
        line.open(targetFormat);


        if (line != null) {
            // Aziono la riproduzione.
            line.start();
            //Continuo fino a che trovo byte da leggere o non viene interrotta la riproduzione dall' utente
            while (nBytesRead != -1&&Running) {

                //Leggo i Byte sulla input stream
                nBytesRead = din.read(data, 0, data.length);

                //Se sono stati letti dei byte li scrivo setto il volume
                //e li scrivo sull' interfaccia.
                if (nBytesRead != -1) {
                    setGain(volume, line);
                    line.write(data, 0, nBytesRead);
                   //Aggiorno i secondi correnti di riproduzione dell' observable.
                    models.setSecond(23.55);
                }
            }
            // La riproduzione viene interrotta e vengono rilasciate le risorse
            line.drain();
            line.stop();
            line.close();
            din.close();
        }
    }

    public void update(Observable o, Object arg) {

        StateObservable mod = (StateObservable) o;
        //Controllo lo stato attuale del subject
        switch (mod.getState()) {

            case StateObservable.START:
                //Avvio player di riproduzione
                startPlayer();
                Running=true;
                break;
            case StateObservable.STOP:
                //Arresto player di riproduzione
                Running = false;
                stoplay();
                break;
            case StateObservable.CHANGEPORT:
                //Cambio porta di ascolto e avvio server sulla nuova porta
                Running=true;
                setPort(mod.getPort());
                break;
            case StateObservable.CHANGEVOLUME:
                //Aggiornamento Valore volume.
                Running = true;
                setVolume(mod.getVolume());
                break;
            default:
                break;
        }

    }


}
